package simco.framework.gui.services;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import javax.swing.JOptionPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

import simco.framework.core.ICore;
import simco.framework.gui.services.data.InfoModel;
import simco.framework.gui.utils.LocalizationUtil;
import simco.framework.gui.utils.LoggingUtil;
import simco.framework.models.CompInfo;
import simco.framework.models.LinkHistoryRecord;
import simco.framework.models.LinkInfo;
import simco.framework.models.MethodInfo;
import simco.framework.models.TypeEvent;

/**
 * Tato trida zajistuje nacitani informaci o libovolnem
 * objektu simulace. Pouziva se k ziskavani informaci
 * o prave vybranen objektu simulace na platne. 
 * 
 * @author Matej Prokop, mythge@students.zcu.cz
 *
 */
public class CoreInformationProvider implements ChangeListener 
{
	//injected by Spring DM
	/**
	 * Datovy model, ve kterem jsou uchovany 
	 * informace o prave vybranem objektu
	 */
	private InfoModel infoModel;
	
	//injected by Spring DM
	/**
	 * Reference na jadro simulace
	 */
	private ICore core;
	
	//injected by Spring DM
	/**
	 * OSGi bundle kontext
	 */
	private BundleContext bundleContext;
	
	@Override
	public void stateChanged(ChangeEvent e) 
	{
		selectedNewObject();
	}

	/**
	 * Obnovi informace o prave vybranem objektu
	 */
	public void refreshInfo()
	{
		switch (infoModel.getSelectedObjectType()) 
		{
			case LINE:
				lineSelected(infoModel.getSelectedObjectId());				
				break;			
			default:
				return;
		}		
	}
	
	/**
	 * Podle aktualne vybraneho objektu na platne nastavi
	 * obsah datoveho modelu, do ktereho jsou uchovany 
	 * informace o prave vybranem objektu
	 */
	public void selectedNewObject()
	{
		switch (infoModel.getSelectedObjectType()) 
		{
			case NOTHING:
				return;
			case LINE:
				lineSelected(infoModel.getSelectedObjectId());				
				break;
			case SIM_COMP:
			case REAL_COMP:
				retraiveCompInfo(infoModel.getSelectedObjectId());
				break;
			case INTERFACE:
				interfaceSelected(infoModel.getSelectedObjectId() - ScenarioLoader.INTERFACE_CONST);
				break;
			default:
				// vybran intermediate: 
				interfaceSelected(infoModel.getSelectedObjectId());				
				break;
		}
	}
	
	/**
	 * Zapise do datoveho modelu obsahujiciho informace o aktualne
	 * vybranem objektu informace o lince s ID prijatym v
	 * parametru metody.
	 * 
	 * @param lineId ID linky jejiz info se zapise
	 */
	private void lineSelected(long lineId)
	{
		/*
		 * Podle ID linky se pozna zda-li se jedna vazbu, 
		 * existuje (je registrovana) v jadre nebo zda-li
		 * je vybrana vazba vygenerovan GUI (vazba mezi 
		 * rozhranim komponenty a komponentou samotnou).
		 */
		if(lineId < ScenarioLoader.LINE_CONST)
		{
			LinkInfo line;
			try 
			{
				// informace o linkce registrovane v jadre jsou nacteny pomoci fce jadra
				line = core.getLinks().get(infoModel.getSelectedObjectId());
			} 
			catch (Exception e) 
			{
				showException(e);
				LoggingUtil.getGuiLogger().error("Pri ziskavani informaci o linkach z jadra doslo k chybe!", e);
				return;
			}
			
			infoModel.changeList( transformRecordToString(line.getHistory(), false) );				
		}
		else
		{
			/*
			 * V tomto pripade byla vybrana linka, ktera neni v jadre registrovana.
			 * Pres tuto linku vede vsechna (prichozi) komunikace s komponentou.
			 * Informace o teto lince je tedy vypisovana jako sjednoceni informaci o
			 * vsech linkach (registrovanych v jadre) vedoucich do teto komponenty:
			 * 
			 */
			Map<Long, LinkInfo> map;
			try
			{
				// nacte se mapa vsech linek registrovanych v jadre
				map = core.getLinks();				
			}
			catch (Exception e) 
			{
				showException(e);
				LoggingUtil.getGuiLogger().error("Pri ziskavani informaci o linkach z jadra doslo k chybe!", e);
				return;
			}
						
			List<LinkHistoryRecord> list = new ArrayList<LinkHistoryRecord>();
			/*
			 * Diky konvenci s identifikatory objektu (i linek) vytvorenych
			 * v GUI, je mozne ziskat z ID linky jednoduse ziskat ID 
			 * komponenty (vlastnici tuto linku) pouze odectenim konstanty:
			 */ 
			long interfaceOwnerId = lineId - ScenarioLoader.LINE_CONST;
			for(long key : map.keySet())
			{
				LinkInfo linkInfo = map.get(key);
				// do vysledku zahrnu vsechny linky vedouci do poz. komponenty
				if(interfaceOwnerId == linkInfo.getTarget())
				{
					retraiveLineHistory(linkInfo, list);
				}
			}
			infoModel.changeList(transformRecordToString(list, true));
		}
	}
	
	/**
	 * Prepise zaznamy o historii volani z linky do
	 * seznamu s historii volani.
	 * 
	 * @param link objekt reprezentujici linku v jadre
	 * @param storage seznam s historii volani
	 */
	private void retraiveLineHistory(LinkInfo link, List<LinkHistoryRecord> storage)
	{
		List<LinkHistoryRecord> history = link.getHistory();
		for(LinkHistoryRecord lhr : history)
		{
			storage.add(lhr);
		}
	}
	
	/**
	 * Prevede seznam s historii volani na seznam retezcu. Pokud je
	 * to pozadovano provede razeni podle casu provedeni volani.
	 * 
	 * @param records seznam s historii volani
	 * @param sort definuje jestli bude provedeno razeni
	 * @return vraci vysleny seznam retezcu
	 */
	private List<String> transformRecordToString(List<LinkHistoryRecord> records,
			boolean sort)
	{
		if(sort)
		{
			Collections.sort(records, new HistoryComparator());
		}
		
		List<String> list = new ArrayList<String>();
		for(LinkHistoryRecord lhr : records)
		{
			if(lhr.getType() == TypeEvent.REAL_RETURN || 
					lhr.getType() == TypeEvent.SIMULATION_RETURN)
			{
				list.add("At " + lhr.getTime() + ": " +
						lhr.getMethodName() + "() return to " + lhr.getTarget()); 
			}
			else
			{
				list.add("At " + lhr.getTime() + ": " + lhr.getTarget() + "." +
						lhr.getMethodName() + "() called");				
			}
		}
		
		return list;
	}
	
	/**
	 * Zapise do info modelu informace o komponent s pozadovanym
	 * ID.
	 *  
	 * @param compId id komponenty jejich info bude zapsano
	 */
	private void retraiveCompInfo(long compId)
	{
		CompInfo info;
		try 
		{
			info = core.getComponents().get(compId);
		} 
		catch (Exception e) 
		{
			showException(e);
			LoggingUtil.getGuiLogger().error("Pri ziskavani informaci z jadra doslo k chybe!", e);
			return;
		}
		
		List<String> list = new ArrayList<String>();
		Bundle bc = bundleContext.getBundle(compId);
		list.add("OSGi Bundle: ");
		list.add("- SymbolicName: " + bc.getSymbolicName());
		list.add("- Bundle ID: " + bc.getBundleId());
		list.add("- Bundle state: " + bundleStateToString(bc.getState()));
		list.add(" ");
		list.add("Component type: " + info.getType());
		list.add(" ");
		list.add("Provided methods:");		
		for(MethodInfo mi : info.getMethodInfo())
		{			
			list.add(methodInfoToString(mi));
		}
		
		infoModel.changeList(list);
	}
	
	/**
	 * Do datoveho modelu uchovavajici informace o aktualne vybranem
	 * objektu zapise metody poskytovane oznacenym rozhranim.
	 * 
	 * @param compId id KOMPONENTY, jejiz rozhrani je oznaceno
	 */
	private void interfaceSelected(long compId)
	{
		CompInfo info;
		try 
		{
			info = core.getComponents().get(compId);
		} 
		catch (Exception e) 
		{
			showException(e);
			LoggingUtil.getGuiLogger().error("Pri ziskavani informaci z jadra doslo k chybe!", e);
			return;
		}
		
		List<String> list = new ArrayList<String>();
		list.add("Provided methods:");
		for(MethodInfo mi : info.getMethodInfo())
		{
			list.add(methodInfoToString(mi));
		}
		
		infoModel.changeList(list);
	}
	
	/**
	 * Vypise dialog s vyjimkou
	 * @param ex vypisovana vyjimka
	 */
	private void showException(Exception ex)
	{
		JOptionPane.showMessageDialog(null,
				LocalizationUtil.getGeneralResource().getString("event.coreError") +
				": " + ex.getMessage() );
	}

	/**
	 * Natavuje datovy model uchovavajici informace o aktualne vybranem
	 * objektu
	 * @param infoModel datovy model uchovavajici informace o 
	 * aktualne vybranem objektu
	 */
	public void setInfoModel(InfoModel infoModel) 
	{
		this.infoModel = infoModel;
	}

	/**
	 * Nastavi jadro simulace
	 * @param core reference na jadro simulace
	 */
	public void setCore(ICore core) 
	{
		this.core = core;
	}

	/**
	 * Nastavi OSGi bundle kontext
	 * @param bundleContext OSGi bundle kontext
	 */
	public void setBundleContext(BundleContext bundleContext) 
	{
		this.bundleContext = bundleContext;
	}
	
	/**
	 * Vrati stav OSGi komponenty ve srozumitelne 
	 * forme, podle cisla definujiciho stav
	 * komponent.
	 * @param state zakodovany stav komponenty cislem
	 * @return dekodovany stav komponenty ve srozumitelne (textove) forme
	 */
	private String bundleStateToString(int state)
	{
		switch (state) 
		{
			case Bundle.ACTIVE:
				return "Active";
			case Bundle.INSTALLED:
				return "Installed";
			case Bundle.RESOLVED:
				return "Resolved";
			case Bundle.UNINSTALLED:
				return "Uninstalled";
			case Bundle.STARTING:
				return "Starting";				
			default:
				return "Stoping";
		}
	}
	
	/**
	 * Prevede objekt uchovajici informace o metode komponenty
	 * na retezec.
	 * 
	 * @param mi objekt uchovajici informace o metode
	 * @return informace o metode ulozene v retezci
	 */
	private String methodInfoToString(MethodInfo mi)
	{
		String temp = "+ " + mi.getClassName() + "." + mi.getMethodName() + "()";
		if(mi.getDelay() > 0)
		{
			temp += "; [delay=" + mi.getDelay() + "]";
		}
		return temp;
	}
	
	/**
	 * Comparator zaznamu historie volani podle casu volani.
	 * 
	 * @author Matej Prokop, mythge@students.zcu.cz
	 */
	class HistoryComparator implements Comparator<LinkHistoryRecord>
	{

		@Override
		public int compare(LinkHistoryRecord arg0, LinkHistoryRecord arg1) 
		{
			if(arg0.getTime() == arg1.getTime())
			{
				return 0;
			}
			else if(arg0.getTime() > arg1.getTime())
			{
				return 1;
			}
			else
			{
				return -1;
			}
		}
		
	}
	
}
